package service

import (
	"crypto/rsa"
	"regexp"
	"sync"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/pkg/errors"

	"bilinadmin/pkg/lueerror"
)

var (
	pubKey  *rsa.PublicKey
	priKey  *rsa.PrivateKey
	pubOnce sync.Once
	priOnce sync.Once
)

type (
	AccountToken struct {
		Mobile   string `json:"mobile"`
		Email    string `json:"email"`
		GID      string `json:"g_id"`
		UserName string `json:"user_name"`
	}

	// bilin token
	BilinToken struct {
		GID     string `json:"g_id"`
		BrandId int64  `json:"brand_id"`
		EmpId   int64  `json:"emp_id"`
		Name    string `json:"name"`
	}
	bilinClaims struct {
		jwt.StandardClaims
		*BilinToken
	}
	accountClaims struct {
		jwt.StandardClaims
		*AccountToken
	}
)

// LoadPubKey 加载公钥
func LoadPubKey(key string) func() {
	return func() {
		pubKey, _ = jwt.ParseRSAPublicKeyFromPEM([]byte(key))
	}
}

// LoadPrivKey 加载私钥
func LoadPrivKey(key string) func() {
	return func() {
		priKey, _ = jwt.ParseRSAPrivateKeyFromPEM([]byte(key))
	}
}

// VerifyAccountToken jwt token 校验
func VerifyBilinToken(tokenStr string) (*BilinToken, error) {
	parse := &jwt.Parser{
		ValidMethods:         nil,
		UseJSONNumber:        false,
		SkipClaimsValidation: true,
	}
	token, err := parse.ParseWithClaims(tokenStr, &bilinClaims{}, func(token *jwt.Token) (i interface{}, e error) {
		return pubKey, nil
	})
	if err != nil {
		return nil, err
	}
	claims := token.Claims.(*bilinClaims)
	err = claims.Valid()
	if err != nil {
		return nil, err
	}
	return claims.BilinToken, nil
}

// VerifyAccountToken jwt token 校验
func VerifyAccountToken(tokenStr string) (*AccountToken, error) {
	parse := &jwt.Parser{
		ValidMethods:         nil,
		UseJSONNumber:        false,
		SkipClaimsValidation: true,
	}
	token, err := parse.ParseWithClaims(tokenStr, &accountClaims{}, func(token *jwt.Token) (i interface{}, e error) {
		return pubKey, nil
	})
	if err != nil {
		return nil, err
	}
	claims := token.Claims.(*accountClaims)
	err = claims.Valid()
	if err != nil {
		return nil, err
	}
	return claims.AccountToken, nil
}

// GenJwtToken jwt token 生成
func GenJwtToken(payload *BilinToken, issuedAt, expiredAt int64) (string, error) {
	t := jwt.New(jwt.SigningMethodRS256)
	t.Claims = &bilinClaims{
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expiredAt,
			IssuedAt:  issuedAt,
		},
		BilinToken: payload,
	}
	return t.SignedString(priKey)
}

// GenJwtToken jwt token 生成
func GenJwtToken2(payload *AccountToken, issuedAt, expiredAt int64) (string, error) {
	t := jwt.New(jwt.SigningMethodRS256)
	t.Claims = &accountClaims{
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expiredAt,
			IssuedAt:  issuedAt,
		},
		AccountToken: payload,
	}
	return t.SignedString(priKey)
}

// 提取 token
func extractToken(token string) string {
	reg, _ := regexp.Compile(`^[bB]earer (([a-zA-z0-9\-_]+\.){2}[a-zA-z0-9\-_]+)$`)
	authReg := reg.FindStringSubmatch(token)
	if len(authReg) != 3 {
		return token
	}
	return authReg[1]
}

// VerifyBilinToken 比邻 token 校验
func (srv *Service) VerifyBilinToken(token string) (*BilinToken, lueerror.LueError) {
	if token == "" {
		return nil, lueerror.AuthGrantErr(errors.New("无效的token"))
	}
	pubOnce.Do(LoadPubKey(srv.cfg.Keys.JWTPublicKey))

	token = extractToken(token)
	payload, err := VerifyBilinToken(token)
	if err != nil {
		return nil, lueerror.AuthGrantErr(err)
	}
	if payload == nil {
		return nil, lueerror.AuthGrantErr(errors.New("jwt payload 为空"))
	}
	return payload, nil
}

// VerifyAccountToken 统一账号 token 校验
func (srv *Service) VerifyAccountToken(token string) (*AccountToken, lueerror.LueError) {
	if token == "" {
		return nil, lueerror.AuthGrantErr(errors.New("无效的token"))
	}
	pubOnce.Do(LoadPubKey(srv.cfg.Keys.JWTPublicKey))
	token = extractToken(token)
	payload, err := VerifyAccountToken(token)
	if err != nil {
		return nil, lueerror.AuthGrantErr(err)
	}
	if payload == nil {
		return nil, lueerror.AuthGrantErr(errors.New("jwt payload 为空"))
	}
	return payload, nil
}

// GenBilinToken 生成 bilin 登陆 token
func (srv *Service) GenBilinToken(GID, Name string, brandId, empId int64) (string, lueerror.LueError) {
	priOnce.Do(LoadPrivKey(srv.cfg.Keys.JWTPrivateKey))
	now := time.Now()
	issuedAt := now.Unix()
	expiredAt := now.Add(srv.cfg.Keys.TokenDuration).Unix()

	payload := &BilinToken{
		GID:     GID,
		BrandId: brandId,
		EmpId:   empId,
		Name:    Name,
	}
	token, err := GenJwtToken(payload, issuedAt, expiredAt)
	if err != nil {
		return "", lueerror.AuthGrantErr(err)
	}
	return token, nil
}
